# 06 - Query 引擎与对话循环

## 两层架构

Claude Code 的 LLM 交互分为两层：

| 层 | 文件 | 职责 |
|----|------|------|
| **会话层** | `QueryEngine.ts` | 管理整个会话生命周期、消息历史、上下文构建 |
| **单轮层** | `query.ts` | 执行一次 API 调用、处理流式响应、调度 Tool |

```
QueryEngine (会话级)
  │
  ├── submit() → 用户发送新消息
  │     │
  │     ├── 构建系统提示
  │     ├── 处理用户输入
  │     └── 调用 query() ← 单轮执行
  │           │
  │           ├── API 流式调用
  │           ├── Tool 调度
  │           └── 循环直到 end_turn
  │
  ├── compact() → 上下文压缩
  │
  └── resume() → 恢复会话
```

---

## QueryEngine.ts — 会话引擎

### 核心职责

1. **会话状态管理**：维护消息历史 (`messages`)
2. **系统提示构建**：调用 `fetchSystemPromptParts()` 组装系统提示
3. **上下文窗口管理**：自动压缩 (auto-compact) 防止超出 token 限制
4. **费用追踪**：累积每轮的 token 使用
5. **会话持久化**：保存/恢复会话到磁盘

### 关键流程：submit()

```
用户输入
  │
  ▼
processUserInput() — 解析输入
  │
  ├─ 斜杠命令 → 分发到 commands
  │
  └─ 普通消息 → 继续
       │
       ▼
  构建系统提示
  ├─ 基础系统提示 (角色定义、行为规则)
  ├─ Tool 描述和使用说明
  ├─ 项目上下文 (CLAUDE.md 内容)
  ├─ 记忆内容 (memdir)
  └─ 系统提醒 (动态注入)
       │
       ▼
  调用 query()
       │
       ▼
  累积 usage, 保存 transcript
       │
       ▼
  检查是否需要 auto-compact
  ├─ 是 → 执行压缩，截断旧消息
  └─ 否 → 继续等待下一次输入
```

### 自动压缩 (Auto-Compact)

当对话 token 数接近模型上下文窗口限制时，自动触发压缩：

```typescript
const autoCompactState = calculateTokenWarningState(...)
if (isAutoCompactEnabled() && shouldAutoCompact(autoCompactState)) {
  await compact()
}
```

压缩通过 `buildPostCompactMessages()` 将历史消息浓缩为摘要。

---

## query.ts — 单轮执行器

### 核心职责

1. **API 调用**：发送消息到 Anthropic API，接收 SSE 流
2. **流式处理**：逐 token 渲染到终端
3. **Tool 调度**：解析 `tool_use` 块，执行对应 Tool
4. **并发控制**：只读 Tool 并行，写入 Tool 串行
5. **中断处理**：用户按 Ctrl+C 或提交新消息时中断

### 核心循环

```
query() 入口
  │
  ▼
  准备消息 (normalizeMessagesForAPI)
  │
  ▼
  ┌─→ 调用 Anthropic API (stream)
  │     │
  │     ▼
  │   接收事件流
  │     │
  │     ├─ text → 渲染文本
  │     ├─ thinking → 渲染思考过程
  │     └─ tool_use → 收集 Tool 调用
  │           │
  │           ▼
  │     执行 Tool (runTools)
  │       ├─ 并发安全的 → 并行执行 (max 10)
  │       └─ 非并发安全的 → 串行执行
  │           │
  │           ▼
  │     Tool 结果 → 构建 tool_result 消息
  │           │
  │           ▼
  │     检查 stop_reason
  │       ├─ end_turn → 退出循环
  │       ├─ tool_use → 继续循环 ─────┘
  │       └─ max_tokens → 继续（可能升级 token 限制）
  │
  ▼
  返回结果
```

### Tool 调度器：StreamingToolExecutor

```typescript
import { StreamingToolExecutor } from './services/tools/StreamingToolExecutor.js'
import { runTools } from './services/tools/toolOrchestration.js'
```

Tool 执行支持**流式启动**——当 API 还在流式返回后续内容时，已完全接收输入的 Tool 可以开始执行。

### Token 限制升级

```typescript
import { ESCALATED_MAX_TOKENS } from './utils/context.js'
```

当响应被 `max_tokens` 截断时，下一轮会自动升级 token 限制，让模型生成更长的响应。

---

## 消息类型

```typescript
type Message =
  | UserMessage           // 用户消息
  | AssistantMessage       // AI 响应
  | AttachmentMessage      // 附件（CLAUDE.md、记忆等）
  | SystemMessage          // 系统消息（仅 UI 显示，不发送到 API）
  //   子类型包括：
  //   - SystemLocalCommandMessage    — 本地命令执行消息
  //   - SystemCompactBoundaryMessage — 压缩边界标记
  //   - SystemMicrocompactBoundaryMessage — 微压缩边界标记
  //   - SystemAPIErrorMessage        — API 错误消息
  //   - SystemMemorySavedMessage     — 记忆保存提示
  //   - SystemAwaySummaryMessage     — 离开摘要消息
  //   - SystemBridgeStatusMessage    — Bridge 状态消息
  //   - SystemAgentsKilledMessage    — Agent 终止消息
  //   等
  | ProgressMessage        // 进度消息
  | ToolUseSummaryMessage  // Tool 使用摘要
  | TombstoneMessage       // 已删除的消息标记
```

消息在发送到 API 前经过 `normalizeMessagesForAPI()` 处理：
- 移除系统消息（UI-only 的消息不发给 API）
- 应用 Tool 结果预算 (`applyToolResultBudget`)
- 移除签名块 (`stripSignatureBlocks`)

### stripSignatureBlocks

`stripSignatureBlocks` 负责在发送消息到 API 前剥离消息内容中的签名块（如邮件签名、PGP 签名等）。这些签名块对模型推理无用，且会占用 token 预算，因此在规范化阶段被移除以节省上下文空间。

---

## Tool 结果预算

大型 Tool 输出不会全部发送给模型：

```typescript
import { applyToolResultBudget } from './utils/toolResultStorage.js'
```

当 Tool 输出超过 `maxResultSizeChars`（每个 Tool 自定义）时：
1. 完整结果保存到磁盘文件
2. 模型只收到**预览 + 文件路径**
3. 模型可以用 FileRead 读取完整结果

例外：`FileReadTool` 的 `maxResultSizeChars = Infinity`——它的输出永远不持久化（否则会产生循环读取）。

---

## 重试机制

```typescript
import { categorizeRetryableAPIError } from './services/api/errors.js'
import { FallbackTriggeredError } from './services/api/withRetry.js'
```

API 调用失败时：
1. 分类错误类型（限流、过载、网络错误）
2. 可重试错误 → 指数退避重试
3. `FallbackTriggeredError` → 切换到备用模型/端点

---

## 关键数据流图

```
用户: "帮我修复 bug"
  │
  ▼
QueryEngine.submit("帮我修复 bug")
  │
  ├── 构建系统提示 (~数千 token)
  │     ├─ 角色定义
  │     ├─ 40+ Tool 描述
  │     ├─ 项目上下文
  │     └─ CLAUDE.md 记忆
  │
  ├── 调用 query()
  │     │
  │     ├── API: 发送消息 (stream: true)
  │     │
  │     ├── AI: "我来看看代码..."
  │     │     tool_use: { name: "Grep", pattern: "bug" }
  │     │
  │     ├── 权限检查: isReadOnly → allow
  │     ├── Grep 执行 → 找到 3 个文件
  │     ├── tool_result 发送回 API
  │     │
  │     ├── AI: "找到了问题，让我修复..."
  │     │     tool_use: { name: "Edit", file: "src/foo.ts" }
  │     │
  │     ├── 权限检查: 写操作 → ask → 用户确认
  │     ├── FileEdit 执行 → 修改文件
  │     ├── tool_result 发送回 API
  │     │
  │     └── AI: "bug 已修复。" (end_turn)
  │
  └── 累积 usage, 保存 transcript
```
